package com.taldh.utils.reflectutil;

import com.google.common.collect.Maps;
import com.taldh.utils.assertutils.AssertUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * date 2020/5/27
 *
 * @author 袁小黑
 * @version 1.0.0
 **/

@Slf4j
public enum  ReflectUtil {

    ;
    ReflectUtil() {}

    public static List<Field> getAllFields(Class<?> clz) {
        List<Field> fields = new ArrayList<>();
        while (!Object.class.equals(clz)) {
            Field[] declaredFields = clz.getDeclaredFields();
            Collections.addAll(fields, declaredFields);
            clz = clz.getSuperclass();
        }
        return fields;
    }

    public static Field getField(Class<?> clz, String fieldName) throws NoSuchFieldException {
        Field declaredField = null;
        Class<?> orgClz = clz;
        while (!Object.class.equals(clz)) {
            try {
                declaredField = clz.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                //ignore exception;    prevent some level throw exception.
            }
            clz = clz.getSuperclass();
        }

        if (declaredField == null) {
            throw new NoSuchFieldException("查找class:" + orgClz.getName() + "的field:" + fieldName + " 失败");
        }

        return declaredField;
    }

    public static Object getFieldValue(Object obj, String fieldName) {
        AssertUtils.isNotNull(obj, "obj must not be null");
        AssertUtils.isNotBlank(fieldName, "fieldName must not be blank");
        Field field = getAccessibleField(obj, fieldName);
        if (field == null) {
            return null;
        }
        try {
            return field.get(obj);
        } catch (IllegalAccessException e) {
            log.error("此Field"+fieldName+"对象"+obj+"实施Java语言访问控制和基础字段是不可访问。", e);
        }
        return null;
    }

    public static Field getAccessibleField(Object obj, String fieldName) {
        AssertUtils.isNotNull(obj, "obj must not be null");
        AssertUtils.isNotBlank(fieldName, "fieldName must not be blank");

        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
            try {
                Field field = superClass.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field;
            } catch (NoSuchFieldException e) {
                log.error("", e);
            }
        }
        return null;
    }

    public static Class getSuperclassGenericType(Class clazz, int index) {
        AssertUtils.isNotNull(clazz, "obj must not be null");
        AssertUtils.isNatureNumber(index, "index must greater than zero");

        Type genType = clazz.getGenericSuperclass();

        if (!(genType instanceof ParameterizedType)) {
            log.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
            return null;
        }

        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

        if ((index >= params.length) || (index < 0)) {
            log.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + params.length);
            return null;
        }
        if (!(params[index] instanceof Class)) {
            log.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
            return Object.class;
        }

        return (Class) params[index];
    }

    public static <T> Class<T> getSuperClassFirstGenericType(Class<T> clazz) {
        return getSuperclassGenericType(clazz, 0);
    }

    public static Object invokeGetterMethod(Object obj, String propertyName) {
        String getterMethodName = "get" + StringUtils.capitalize(propertyName);
        return invokeMethod(obj, getterMethodName, new Class[0], new Object[0]);
    }
    
    public static void invokeSetterMethod(Object obj, String propertyName, Object value) {
        invokeSetterMethod(obj, propertyName, value, null);
    }

    public static void invokeSetterMethod(Object obj, String propertyName, Object value, Class<?> propertyType) {
        Class<?> type = propertyType != null ? propertyType : value.getClass();
        String setterMethodName = "set" + StringUtils.capitalize(propertyName);
        invokeMethod(obj, setterMethodName, new Class[]{type}, new Object[]{value});
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) {
        Field field = getAccessibleField(obj, fieldName);

        if (field == null) {
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
        }
        try {
            field.set(obj, value);
        } catch (IllegalAccessException e) {
            log.error("不可能抛出的异常:{}", e.getMessage());
        }
    }

    public static Object invokeMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object[] args) {
        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
        if (method == null) {
            throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
        }
        try {
            return method.invoke(obj, args);
        } catch (Exception e) {
            throw convertReflectionExceptionToUnchecked(e);
        }
    }


    public static Method getAccessibleMethod(Object obj, String methodName, Class<?>... parameterTypes) {
        if (obj == null) {
            log.error("ReflectionUtil.getAccessibleField(final Object obj, final String property)的obj参数不能为空");
            throw new NullPointerException("object不能为空");
        }

        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
            try {
                Method method = superClass.getDeclaredMethod(methodName, parameterTypes);

                method.setAccessible(true);

                return method;
            } catch (NoSuchMethodException e) {
                log.error("NoSuchMethodException!", e);
            }
        }
        return null;
    }


    public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
        if (((e instanceof IllegalAccessException)) || ((e instanceof IllegalArgumentException)) || ((e instanceof NoSuchMethodException))) {
            return new IllegalArgumentException("Reflection Exception.", e);
        }
        if ((e instanceof InvocationTargetException)) {
            return new RuntimeException("Reflection Exception.", ((InvocationTargetException) e).getTargetException());
        }
        if ((e instanceof RuntimeException)) {
            return (RuntimeException) e;
        }
        return new RuntimeException("Unexpected Checked Exception.", e);
    }

    public static Map<String ,Object> getStaticField(Class clazz) throws Exception {
        Map<String ,Object> fieldMap = Maps.newHashMap();

        Field[] fields = clazz.getDeclaredFields();
        if (fields == null || fields.length <= 0) {
            return fieldMap;
        }

        for (Field field : fields) {
            field.setAccessible(true);
            if (Modifier.isStatic(field.getModifiers())) {
                fieldMap.put(field.getName() , field.get(clazz));
            }
        }
        return fieldMap;
    }

    public static Object getStaticField(Class clazz , String staticFieldName) {
        Object obj = null;

        Field[] fields = clazz.getDeclaredFields();
        if (fields == null || fields.length <= 0) {
            return obj;
        }

        try {
            for (Field field : fields) {
                field.setAccessible(true);
                String fieldName = field.getName();
                if (Modifier.isStatic(field.getModifiers()) && fieldName.equals(staticFieldName)) {
                    obj = field.get(clazz);
                }
            }
        }catch (Exception e){
            obj = null;
        }
        return obj;
    }
}
